﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Text;
using Isuka.Linq;
using Isuka.Reflection;

namespace Isuka.Cola.Auxiliaries
{
    internal sealed class DelegateTypeBuilder
    {
        const string ASSEMBLY_NAME = "Isuka_Cola_DynamicAssembly";
        const string MODULE_NAME = "Isuka_Cola_DynamicModule";

        #region Fields

        readonly Stack<Type> typeStak = new Stack<Type>();
        readonly Stack<ArgumentInfo> argStack = new Stack<ArgumentInfo>();

        readonly AssemblyBuilder assemblyBuilder;
        readonly ModuleBuilder moduleBuilder;

        readonly DelegateTypeInfo info = new DelegateTypeInfo();

        #endregion

        public DelegateTypeBuilder()
        {
            var assembly = new AssemblyName()
            {
                Version = new Version(1, 0, 0, 0),
                Name = ASSEMBLY_NAME,
            };
            assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.Run);
            moduleBuilder = assemblyBuilder.DefineDynamicModule("Isuka_DynamicModule");
        }

        public void ComitArgument()
        {
            Gate.NotEmpty(argStack);
            Gate.NotEmpty(typeStak);

            var a = argStack.Pop();

            a.Type = typeStak.Pop();
            if (a.ByRef)
            {
                a.Type = a.Type.MakeByRefType();
            }
            info.Arguments.Add(a);
        }

        public Type CreateDelegateType()
        {
            Contract.Ensures(Contract.Result<Type>() != null);

            var typeAttr
                = TypeAttributes.Class
                | TypeAttributes.Public
                | TypeAttributes.Sealed
                | TypeAttributes.AnsiClass
                | TypeAttributes.AutoClass
                ;
            var typeBuilder = moduleBuilder.DefineType(info.Name, typeAttr, typeof(MulticastDelegate));

            var ctorAttr
                = MethodAttributes.RTSpecialName
                | MethodAttributes.HideBySig
                | MethodAttributes.Public
                | MethodAttributes.SpecialName
                ;
            var ctorBuilder = typeBuilder.DefineConstructor(ctorAttr
                            , CallingConventions.Standard
                            , new[] { typeof(object), typeof(IntPtr) });
            ctorBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);

            var argTypes = info.Arguments.Select(x => x.Type).ToArray();
            var methodAttr
                = MethodAttributes.Public
                | MethodAttributes.HideBySig
                | MethodAttributes.NewSlot
                | MethodAttributes.Virtual
                ;
            var methodBuilder = typeBuilder.DefineMethod("Invoke", methodAttr, info.ResultType, argTypes);
            methodBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);
            info.Arguments.ForEach((x, i) =>
            {
                var paramBuilder = methodBuilder.DefineParameter(i, x.ParamAttributes, x.Name);
                x.Attributes.ForEach(y => paramBuilder.SetCustomAttribute(y));
            });
            info.Attributes.ForEach(x =>
            {
                typeBuilder.SetCustomAttribute(x);
            });

            var type = typeBuilder.CreateType();
            Contract.Assume(type != null);
            return type;
        }

        internal ArgumentInfo CurrentArg
        {
            get
            {
                Contract.Ensures(Contract.Result<ArgumentInfo>() != null);

                Gate.NotEmpty(argStack);
                return argStack.Peek();
            }
        }

        public DelegateTypeInfo DelegateInfo
        {
            get { return info; }
        }

        public void NewArgument()
        {
            argStack.Push(new ArgumentInfo());
        }

        public void PushType(Type type)
        {
            typeStak.Push(type);
        }

        public void SetResultType()
        {
            Gate.NotEmpty(typeStak);

            info.ResultType = typeStak.Pop();
        }

        #region ObjectInvariant

        [ContractInvariantMethod]
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(moduleBuilder != null);
            Contract.Invariant(typeStak != null);
            Contract.Invariant(argStack != null);
            Contract.Invariant(info != null);
        }


        #endregion
    }
}
